﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Drawing;

using LowLevelGraphics;
using LowLevelGraphics.Types.Interval;

namespace LowLevelGraphics.Extractors
{
    /// <summary>
    /// 
    /// </summary>
    public enum DIRECTION_SNAKE_MODE
    {
        /// <summary>
        /// This will execute the snake as a contour tracer, i.e. 
        /// the FindStartPoint-Function will find the first matching point
        /// and then trace the contour of all pixels, till it ends at the start point again
        /// </summary>
        TRACE_ON_CONTOUR,

        /// <summary>
        /// This will look up pixels in m_Color that are LEFT TO the current pixel
        /// </summary>
        TRACE_LEFT_PIXELS,
        
        /// <summary>
        /// This will look up pixels in m_Color that are RIGHT TO the current pixel
        /// </summary>
        TRACE_RIGHT_PIXELS
    }

    /// <summary>
    /// Use this to extract blobs from a picture by tracing their contours.
    /// Momentarily this will work with a pixel falling down like a waterdrop from heaven.
    /// Then when this pixel hits the given color, the found contour is traced till to an end.
    /// This end may be reached if we get back 
    /// </summary>
    public class BugFollower : IEnumerable
    {
        protected DIRECTION_SNAKE_MODE m_SNAKE_MODE = DIRECTION_SNAKE_MODE.TRACE_ON_CONTOUR;
        protected List<Blob> m_aBlob = new List<Blob>();
        protected PROCESSING_MODE m_PROCESSING_MODE = PROCESSING_MODE.Color;

        protected UnsafeBitmap m_Bitmap = null;
        protected bool[,] m_aMark = null;
        protected Color m_Color = Color.Transparent;
        protected Point m_NoPoint = new Point(-1, -1);
        protected int m_nHeight = -1;
        protected int m_nWidth = -1;
        protected Dictionary<int, int[]> m_aDirection = new Dictionary<int, int[]>();
        protected Point[] m_aPoint = Definitions.EnvironmentPointListClockwise.ToArray();

        /// <summary>
        /// Initializes a new instance of the <see cref="DirectedSnakeExtractor"/> class.
        /// </summary>
        /// <param name="_bitmap">The _bitmap.</param>
        /// <param name="_color">The _color.</param>
        /// <param name="_processing_Mode">The _processing_ mode.</param>
        public BugFollower(UnsafeBitmap _bitmap, Color _color)
            : this(_bitmap, _color, PROCESSING_MODE.Color)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Snake"/> class.
        /// </summary>
        /// <param name="_bitmap">The _bitmap.</param>
        public BugFollower(UnsafeBitmap _bitmap, Color _color, PROCESSING_MODE _processing_Mode)
        {
            m_nHeight = _bitmap.Height;
            m_nWidth = _bitmap.Width;
            m_Bitmap = _bitmap;
            m_Color = _color;
            m_aMark = new bool[m_nWidth, m_nHeight];

            m_PROCESSING_MODE = _processing_Mode;
            
            InitializeDirections();
        }

        /// <summary>
        /// Initializes the directions.
        /// </summary>
        protected void InitializeDirections()
        {
            m_aDirection.Add(0, new int[] { 0, 1, 2, 6, 7 });
            m_aDirection.Add(1, new int[] { 1, 2, 3, 0, 7 });
            m_aDirection.Add(2, new int[] { 0, 1, 2, 3, 4 });
            m_aDirection.Add(3, new int[] { 1, 2, 3, 4, 5 });
            m_aDirection.Add(4, new int[] { 2, 3, 4, 5, 6 });
            m_aDirection.Add(5, new int[] { 3, 4, 5, 6, 7 });
            m_aDirection.Add(6, new int[] { 4, 5, 6, 7, 0 });
            m_aDirection.Add(7, new int[] { 7, 0, 1, 5, 6 });
        }

        /// <summary>
        /// Resets this instance.
        /// </summary>
        public void Reset()
        {
            m_aBlob.Clear();
            m_aPoint = Definitions.EnvironmentPointListClockwise.ToArray();
        }

        /// <summary>
        /// Gets the blobs.
        /// </summary>
        /// <value>The blobs.</value>
        public List<Blob> Blobs
        {
            get { return m_aBlob; }
        }

        /// <summary>
        /// Create multiple snakes
        /// </summary>
        /// <param name="_aInt">The _a int.</param>
        public void Create(IEnumerable<Point> _aInt)
        {
            foreach (Point ptStart in _aInt)
            {
                Create(ptStart);
            }
        }

        /// <summary>
        /// Creates the specified _PT start.
        /// </summary>
        /// <param name="_ptStart">The _PT start.</param>
        /// <returns></returns>
        public List<Point> Create(Point _ptStart)
        {
            return Create(_ptStart.X, _ptStart.Y);
        }

        /// <summary>
        /// Creates this instance.
        /// </summary>
        /// <returns></returns>
        public List<Point> Create(int _nX, int _nY)
        {
            List<Point> aSnake = new List<Point>();

            Point ptStart = FindStartPoint(_nX, _nY);
            if (ptStart == m_NoPoint) return null;
            int nDirection = 0;

            switch (m_SNAKE_MODE)
            {
                case DIRECTION_SNAKE_MODE.TRACE_ON_CONTOUR:
                    aSnake = TraceOnContour(aSnake, ref ptStart, ref nDirection);
                    break;

                case DIRECTION_SNAKE_MODE.TRACE_LEFT_PIXELS:
                    aSnake = TraceLeftPixels(aSnake, ptStart);
                    break;

                case DIRECTION_SNAKE_MODE.TRACE_RIGHT_PIXELS:
                    aSnake = TraceRightPixels(aSnake, ptStart);
                    break;
            }

            return aSnake;
        }

        /// <summary>
        /// Traces the left pixels.
        /// </summary>
        /// <param name="_aSnake">The _a snake.</param>
        /// <param name="_ptStart">The _PT start.</param>
        /// <returns></returns>
        protected List<Point> TraceLeftPixels(List<Point> _aSnake, Point _ptStart)
        {
            return null;
        }

        /// <summary>
        /// Traces the right pixels.
        /// </summary>
        /// <param name="_aSnake">The _a snake.</param>
        /// <param name="_ptStart">The _PT start.</param>
        /// <returns></returns>
        protected List<Point> TraceRightPixels(List<Point> _aSnake, Point _ptStart)
        {
            return null;
        }

        /// <summary>
        /// Traces ON the contour.
        /// </summary>
        /// <param name="aSnake">A snake.</param>
        /// <param name="ptStart">The pt start.</param>
        /// <param name="nDirection">The n direction.</param>
        /// <returns></returns>
        protected List<Point> TraceOnContour(List<Point> aSnake, ref Point ptStart, ref int nDirection)
        {
            int nXStart = ptStart.X;
            int nYStart = ptStart.Y;
            Point point = new Point(nXStart, nYStart);
            point = GetNextConnectedPixel(ptStart, ref nDirection);

            if (ptStart == m_NoPoint)
                return null;

            Point ptLast = new Point(nXStart, nYStart);

            bool bNeedToStop = false;

            int nIterations = 0;

            int nMaxErrorCount = 1000;
            int nErrorCount = 0;

            while (!bNeedToStop)
            {
                if (aSnake.Count > 89)
                {
                }

                point = GetNextConnectedPixel(point, ref nDirection);
                if (point == ptStart || point == m_NoPoint)
                {
                    bNeedToStop = true;
                    break;
                }

                if (!aSnake.Contains(point) && point != m_NoPoint)
                {
                    aSnake.Add(point);
                }
                else
                {
                    Array.Reverse(m_aPoint, 0, m_aPoint.Length);
                }
                nIterations++;
            }

            PointListEvaluator pointListEvaluator = new PointListEvaluator(aSnake);
            Rectangle rectangle = Rectangle.FromLTRB(pointListEvaluator.XInterval.Min, pointListEvaluator.YInterval.Min, pointListEvaluator.XInterval.Max, pointListEvaluator.YInterval.Max);

            int nLabel = 0;
            Blob blob = new Blob(nLabel++);
            blob.SpannedRectangle = rectangle;
            blob.m_aPoint.AddRange(aSnake);
            m_aBlob.Add(blob);

            return aSnake;
        }

        /// <summary>
        /// Finds the start point.
        /// </summary>
        /// <returns></returns>
        private Point FindStartPoint(int _nXStart, int _nYStart)
        {
            bool bFound = false;
            Color color = Color.Transparent;
            int nYStart = 0;
            //Find start point
            for (int y = _nYStart; y < m_nHeight; y++)
            {
                color = m_Bitmap.GetPixel(_nXStart, y);
                if (color == m_Color)
                {
                    nYStart = y;
                    bFound = true;
                    break;
                }
            }
            if (bFound)
            {
                return new Point(_nXStart, nYStart);
            }
            else
            {
                return m_NoPoint;
            }
        }

        /// <summary>
        /// Gets the next connected pixel.
        /// </summary>
        /// <param name="_point">The _point.</param>
        /// <param name="_nStartDirection">The _n start direction.</param>
        /// <returns></returns>
        public Point GetNextConnectedPixel(Point _point, ref int _nStartDirection)
        {
            int nX = _point.X;
            int nY = _point.Y;

            int nXTemp = -1;
            int nYTemp = -1;

            Point pointTemp = m_NoPoint;

            IEnumerable<int> aInt = m_aDirection[_nStartDirection];
            foreach(int i in aInt)
            {
                pointTemp = m_aPoint[i];
                nXTemp = nX + pointTemp.X;
                nYTemp = nY + pointTemp.Y;

                if (nXTemp < 0 || nYTemp < 0 || nYTemp > m_nHeight - 1 || nXTemp > m_nWidth - 1) continue;

                if (m_Bitmap.GetPixel(nXTemp, nYTemp) == m_Color)
                {
                    _nStartDirection = i;
                    Debug.WriteLine(new Point(nXTemp, nYTemp).ToString() + " " + _nStartDirection);
                    return new Point(nXTemp, nYTemp);
                }
            }
            return new Point(-1, -1);
        }

        #region IEnumerable Members

        /// <summary>
        /// Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
        /// </returns>
        public IEnumerator GetEnumerator()
        {
            return this.m_aBlob.GetEnumerator();
        }

        #endregion
    }
}
